﻿using Devil;
using Excel;
using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Linq;

namespace TableCore
{
    public partial class GTFile
    {
        public static int GetMaxRowIndex(DataTable table)
        {
            var cols = table.Columns.Count;
            if (cols <= START_COL)
                return 0;
            var c0 = START_ROW;
            var c1 = table.Rows.Count - 1;
            var max = c1;
            while (c0 <= c1)
            {
                var c = (c0 + c1) >> 1;
                var id = Utils.GetString(table.Rows[c]);
                if (string.IsNullOrEmpty(id))
                {
                    c1 = c - 1;
                    max = c1;
                }
                else
                {
                    c0 = c + 1;
                }
            }
            return max;
        }

        public void UploadFillVariantsFile(string file, bool useFilter)
        {
            if (string.IsNullOrEmpty(file))
                return;
            lock (_lock)
            {
                if (mStopUpload)
                    throw new Exception("已经结束文件上传，请确保在 FinishUpload 之前完成上传工作.");
                InitFilters();
                var pname = !useFilter || mFilters == null || mFilters.Count == 0 ? file : file.Replace('\\', '/');
                var pass = !useFilter || mFilters == null || FileFilter.PassDataFile(pname, mFilters);
                if (!pass)
                    return;
                var path = Path.GetFullPath(file);
                if (!mFiles.Add(path))
                    return;
                var cmd = new FillVariantCmd(this, file);
                mUploadCmd = ParallelUtils.Schedule(cmd, false, mUploadCmd);
            }
        }

        class VariantHeader
        {
            internal readonly string name;
            internal readonly string[] variants;
            internal readonly int[] atCols;

            public VariantHeader(string name, string[] variants)
            {
                this.name = name;
                this.variants = variants;
                this.atCols = new int[variants.Length];
            }
        }

        class VariantRow
        {
            internal readonly int rowIndex;
            internal readonly VariantHeader header;
            internal readonly string[] values;
            public VariantRow(VariantHeader header, int row)
            {
                rowIndex = row;
                this.header = header;
                values = new string[header.variants.Length];
            }

            bool MatchRowAndGetData(int index, DataRow row)
            {
                var data = Utils.GetString(row[header.atCols[index]]);
                if (string.IsNullOrEmpty(data))
                    return false;
                for (int i = 0; i < values.Length; i++)
                {
                    if (i == index || string.IsNullOrEmpty(values[i]))
                        continue;
                    var str = Utils.GetString(row[header.atCols[i]]);
                    if (!string.IsNullOrEmpty(str) && str == values[i])
                    {
                        values[index] = data;
                        return true;
                    }
                }
                return false;
            }

            public bool FindValues(DataTable table, int maxRowIndex)
            {
                int num = 0;
                var row = table.Rows[rowIndex];
                for (int i = 0; i < values.Length; i++)
                {
                    values[i] = Utils.GetString(row[header.atCols[i]]);
                    if (string.IsNullOrEmpty(values[i]))
                        num++;
                }
                if(num > 0 && num < values.Length)
                {
                    num = 0;
                    for (int i = 0; i < values.Length; i++)
                    {
                        if (!string.IsNullOrEmpty(values[i]))
                            continue;
                        for (int r = START_ROW + 3; r <= maxRowIndex; r++)
                        {
                            if (r == rowIndex)
                                continue;
                            row = table.Rows[r];
                            if (MatchRowAndGetData(i, row))
                                num++;
                        }
                    }
                }
                else
                {
                    num = 0;
                }
                return num > 0;
            }
        }


        class FillVariantInfo
        {
            internal readonly int mMaxRowIndex;
            Content mContent;
            DataTable mTable;
            internal readonly List<VariantHeader> mHeaders;
            internal readonly LinkedList<VariantRow> mRows;

            public FillVariantInfo(Content content)
            {
                mContent = content;
                mTable = content.Data.Tables[content.TableIndex];
                mMaxRowIndex = GetMaxRowIndex(mTable);
                mHeaders = new List<VariantHeader>();
                mRows = new LinkedList<VariantRow>();
            }

            public void GetVariantsHeader()
            {
                mHeaders.Clear();
                var cmod = mContent.Cmodel;
                HashSet<string> variants = new HashSet<string>();
                for (int i = 0; i < cmod.PropertyCount; i++)
                {
                    var p = cmod.GetProperty(i);
                    variants.Clear();
                    p.GetVariants(variants);
                    if (variants.Count == 0)
                        continue;
                    mHeaders.Add(new VariantHeader(p.Name, variants.ToArray()));
                }
                var cols = mTable.Columns.Count;
                var row = mTable.Rows[START_ROW];
                for (int i = 0; i < mHeaders.Count; i++)
                {
                    var head = mHeaders[i];
                    for (int k = 0; k < head.variants.Length; k++)
                    {
                        head.atCols[k] = -1;
                        var key = ParallelUtils.Concat(head.name, '.', head.variants[k]);
                        for (int c = START_COL; c < cols; c++)
                        {
                            if (Utils.GetString(row[c]) == key)
                            {
                                head.atCols[k] = c;
                                break;
                            }
                        }
                    }
                }
            }

            public void GetRowData(int dataIndex)
            {
                for (int i = 0; i < mHeaders.Count; i++)
                {
                    var variantrow = new VariantRow(mHeaders[i], dataIndex + START_ROW + 3);
                    if (variantrow.FindValues(mTable, mMaxRowIndex))
                    {
                        lock (mRows)
                        {
                            mRows.AddLast(variantrow);
                        }
                    }
                }
            }

            public int UpdateTableData()
            {
                var node = mRows.First;
                int num = 0;
                while(node != null)
                {
                    var variantRow = node.Value;
                    node = node.Next;
                    var row = mTable.Rows[variantRow.rowIndex];
                    for (int i = 0; i < variantRow.values.Length; i++)
                    {
                        row[variantRow.header.atCols[i]] = variantRow.values[i] ?? "";
                        num++;
                    }
                }
                return num;
            }
        }


        class FillVariantCmd : ICommand
        {
            string file;
            GTFile gtFile;

            public FillVariantCmd(GTFile gtFile, string file)
            {
                this.gtFile = gtFile;
                this.file = file;
            }

            void UpdateAndSaveDatas(DataSet set, List<FillVariantInfo> infos)
            {
                var num = 0;
                for (int i = 0; i < infos.Count; i++)
                {
                    num += infos[i].UpdateTableData();
                }
                if (num > 0)
                {
                    SaveTablesAsExcel(set, file);
                    Debug.LogFormat("find {0} variants data and \"{1}\" was updated", num, file);
                }
                else
                {
                    Debug.LogFormat("don't find any vairant data in file \"{0}\"", file);
                }
            }

            public void Execute()
            {
                if (string.IsNullOrEmpty(file) || !File.Exists(file))
                    throw new FileNotFoundException("GTFile Can't open file: " + file, file);
                List<Content> tables = new List<Content>();
                List<FillVariantInfo> fills = new List<FillVariantInfo>();
                DataSet data = null;
                using (var stream = new FileStream(file, FileMode.Open))
                {
                    IExcelDataReader reader = null;
                    try
                    {
                        if (file.EndWithIgnoreCase(".xls"))
                        {
                            reader = ExcelReaderFactory.CreateBinaryReader(stream);
                        }
                        else if (file.EndWithIgnoreCase(".xlsx"))
                        {
                            reader = ExcelReaderFactory.CreateOpenXmlReader(stream);
                        }
                        reader.IsFirstRowAsColumnNames = false;
                        while (reader.Read()) { }
                        data = reader.AsDataSet();
                        stream.Close();
                        var sheets = data.Tables;
                        for (int i = 0; i < sheets.Count; i++) // (var table in data.Tables)
                        {
                            var table = sheets[i];
                            var cmod = new ClassModel(table.TableName);
                            cmod.GetProperties(gtFile.mValueFilters, table, START_ROW, START_COL);
                            var content = new Content(gtFile, file, data, cmod, i);
                            tables.Add(content);
                            fills.Add(new FillVariantInfo(content));
                        }
                        lock (gtFile._lock)
                        {
                            gtFile.mDatas.Add(data);
                            gtFile.mContents.AddRange(tables);
                        }
                        //Debug.LogFormat("upload file: {0}", action.file);
                        
                    }
                    catch (Exception e)
                    {
                        Debug.LogFormat("open \"{0}\" error.\n{1}", file, e);
                        return;
                    }
                }
                ParallelDispatcher.CombinedHandler export = new ParallelDispatcher.CombinedHandler("fill variants");
                for (int i = 0; i < tables.Count; i++)
                {
                    var fill = new FillVariantInfo(tables[i]);
                    fills.Add(fill);
                    var job = ParallelUtils.Schedule(fill.GetVariantsHeader, false);//, Factory.GetInstance().GetInitializeHandler());
                    job = ParallelUtils.ScheduleParallel(fill.GetRowData, fill.mMaxRowIndex + 1 - START_ROW - 3, job);
                    export.Combine(job);
                }
                var finish = ParallelUtils.Schedule(() => UpdateAndSaveDatas(data, fills), false, export.GetHandler());
                var clean = ParallelUtils.Schedule(gtFile.DisposeTable, data, false, finish);
                gtFile.mExportCmd.Combine(clean);
            }
        }

    }
}
